﻿namespace SuperModel
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Reflection;
    using System.Reflection.Emit;
    using Generation;

    /// <summary>
    /// A class that implements SuperModel specifications
    /// </summary>
    internal class ImplementationCache
    {
        /// <summary>
        /// The Signed Dynamic Assembly Name
        /// </summary>
        public const string StrongName = "SuperModel.Dynamic, PublicKey=0024000004800000940000000602000000240000525341310004000001000100598dab2293e64dce41d7233e8c042c1ce43bfd40a23b718213bf5a34ed92cd4ed67c4ae00167982fc69f909972b4aa1c77aa23529dcd754583838273c0be288f9a038e63ccf4d231d1e321413cb9ca88f6651cb956871a38f4f6e8396f128238e1a99c45537924b7e2145c610156291381afe3160569aa0adbb56edc52e906ca";

        /// <summary>
        /// The Dynamic Assembly Name
        /// </summary>
        private const string DynamicAssemblyName = "SuperModel.Dynamic";

        /// <summary>
        /// The Assembly Name
        /// </summary>
        private readonly AssemblyName assemblyName = new AssemblyName(DynamicAssemblyName) { Version = new Version("1.0.0.0") };

        /// <summary>
        /// The Implementation type Namer
        /// </summary>
        private readonly TypeNamer typeNamer = new TypeNamer();
        
        /// <summary>
        /// The implemented types
        /// </summary>
        private readonly List<Type> implementedTypes = new List<Type>();

        /// <summary>
        /// The Dynamic Assembly Builder
        /// </summary>
        private readonly AssemblyBuilder dynamicAssembly;

        /// <summary>
        /// The Dynamic Module
        /// </summary>
        private readonly ModuleBuilder dynamicModule;

        /// <summary>
        /// The target type validator.
        /// </summary>
        private readonly TargetTypeValidator interfaceValidator = new TargetTypeValidator();

        /// <summary>
        /// The type implementer
        /// </summary>
        private readonly TypeImplementer typeImplementer = new TypeImplementer();

        /// <summary>
        /// Initializes a new instance of the <see cref="ImplementationCache"/> class. 
        /// </summary>
        public ImplementationCache()
        {
            assemblyName.SetPublicKey(Assembly.GetExecutingAssembly().GetName().GetPublicKey());

            // Create the Assembly and Module
            dynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave, AppDomain.CurrentDomain.Evidence);
            dynamicModule = dynamicAssembly.DefineDynamicModule(assemblyName.Name, assemblyName.Name + ".dll", true);
        }

        /// <summary>
        /// Creates an Instance of the Target Type
        /// </summary>
        /// <typeparam name="T">
        /// The target type
        /// </typeparam>
        /// <returns>
        /// An implementation of the target type
        /// </returns>
        /// <exception cref="InvalidSpecificationException">
        /// Thrown if the type to create is not an interface
        /// </exception>
        public Type CreateImplementationType<T>()
            where T : INotifyPropertyChanged
        {
            // Ensure that we are creating the model based on an interface specification
            if (!typeof(T).IsInterface)
            {
                throw new InvalidSpecificationException("Target type must be an interface");
            }

            Type foundType = null;

            // Try to Find the Type in thy Dynamic Module
            foreach (Type currentType in implementedTypes)
            {
                if (TypeMatches(typeof(T), currentType))
                {
                    foundType = currentType;
                    break;
                }
            }

            // If the Type was Not Found, Create it
            if (foundType == null)
            {
                // Auto-Generate the Type, add it to the collection
                foundType = ImplementInterface(typeof(T));
                implementedTypes.Add(foundType);
            }

            // Create a Default Implemantation of the Type
            return foundType;
        }

        /// <summary>
        /// Does a found type match the requested type??
        /// </summary>
        /// <param name="requestedType">The type being requested</param>
        /// <param name="currentType">The candidate type</param>
        /// <returns>A value indicating whether or not the current type is a valid match for the requested type</returns>
        private static bool TypeMatches(Type requestedType, Type currentType)
        {
            // Make sure the requested type is assignable
            if (requestedType.IsAssignableFrom(currentType))
            {
                IList<Type> requestedTypeInterfaces = new List<Type>(requestedType.GetInterfaces());

                // Make sure the found type's interfaces are in the requested type
                foreach (Type currentInterface in currentType.GetInterfaces())
                {
                    // The current interface is not equal to the requested Type AND
                    // The current interface does not exist anywhere in the requested Type
                    if (currentInterface != requestedType && !requestedTypeInterfaces.Contains(currentInterface))
                    {
                        return false;
                    }
                }

                // We found the correct implementation
                return true;
            }

            return false;
        }

        /// <summary>
        /// Auto-Generate an Implementation of a Data-Model Interface
        /// </summary>
        /// <param name="typeToImplement">The type to implement (an interface type)</param>
        /// <returns>An implementation of the type</returns>
        private Type ImplementInterface(Type typeToImplement)
        {
            // Check that the type to implement makes sense
            string verificationMessage;
            if (!interfaceValidator.Validate(typeToImplement, out verificationMessage))
            {
                throw new InvalidSpecificationException(verificationMessage);
            }

            // Implement the Type
            typeImplementer.ImplementType(typeToImplement, dynamicModule);

            // Load the assembly that's been saved
            return dynamicModule.GetType(typeNamer.GetImplementationName(typeToImplement));
        }
    }
}
